package cn.juque.workflow.helper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.exception.AppException;
import cn.juque.workflow.api.enums.ProcessInstanceStatusEnum;
import cn.juque.workflow.api.enums.VariableEnum;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.history.HistoricActivityInstanceQuery;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>ProcessInfoHelper</li>
 * </ul>
 * @date 2023-02-04 15:26:13
 **/
@Slf4j
@Component("processInfoHelper")
public class ProcessInfoHelper {

    @Resource
    private IdentityService identityService;

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private TaskService taskService;

    @Resource
    private HistoryService historyService;

    /**
     * 获取流程定义的xml
     *
     * @param deployId 部署ID
     * @return string
     */
    public String getResourceByDeployId(String deployId) {
        ProcessDefinition processDefinition = this.repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        try (InputStream inputStream = this.repositoryService.getResourceAsStream(deployId, processDefinition.getResourceName())) {
            return IoUtil.read(inputStream, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new AppException(MessageEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 获取流程定义的xml
     *
     * @param processDefinitionId 流程定义ID
     * @return string
     */
    public String getResourceByProcessDefinitionId(String processDefinitionId) {
        ProcessDefinition processDefinition = this.repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId).singleResult();
        return this.getResourceByDeployId(processDefinition.getDeploymentId());
    }

    /**
     * 创建流程实例
     *
     * @param processDefinitionKey 流程KEY
     * @param businessKey          业务ID
     * @param initiator            发起人
     * @param variableMap          变量
     * @return 实例id
     */
    public String createInstance(String processDefinitionKey, String businessKey, String initiator, Map<String, Object> variableMap) {
        this.identityService.setAuthenticatedUserId(initiator);
        if (MapUtil.isEmpty(variableMap)) {
            variableMap = MapUtil.newHashMap();
        }
        variableMap.put(VariableEnum.INITIATOR.getValue(), initiator);
        ProcessInstance processInstance = this.runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variableMap);
        log.info("create process instance successful, instance id:{}", processInstance.getProcessInstanceId());
        return processInstance.getProcessInstanceId();
    }

    public void deleteInstance(String instanceId, String deleteReason) {
        this.runtimeService.deleteProcessInstanceIfExists(instanceId, deleteReason, false, false, false, false);
    }

    /**
     * 获取实例信息
     *
     * @param processInstanceId 实例ID
     * @return ProcessInstance
     */
    public ProcessInstance getInstance(String processInstanceId) {
        ProcessInstance processInstance = this.runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (null == processInstance) {
            throw new AppException(MessageEnum.PARAM_ERROR.getCode(), "流程实例不存在");
        }
        return processInstance;
    }

    /**
     * 获取历史流程实例
     *
     * @param processInstanceId 实例ID
     * @return HistoricProcessInstance
     */
    public HistoricProcessInstance getHistoricInstance(String processInstanceId) {
        return this.historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
    }

    /**
     * 获取业务ID
     *
     * @param processInstanceId 实例ID
     * @return string
     */
    public String getBusinessId(String processInstanceId) {
        ProcessInstance processInstance = this.runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (null != processInstance) {
            return processInstance.getBusinessKey();
        }
        HistoricProcessInstance historicProcessInstance = this.getHistoricInstance(processInstanceId);
        if (null != historicProcessInstance) {
            return historicProcessInstance.getBusinessKey();
        }
        throw new AppException(MessageEnum.PARAM_ERROR.getCode(), "流程实例不存在");
    }

    /**
     * 获取当前实例状态
     *
     * @param processInstanceId 实例ID
     * @return ProcessInstanceStatusEnum
     */
    public ProcessInstanceStatusEnum getInstanceStatus(String processInstanceId) {
        ProcessInstance processInstance = this.runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (null == processInstance) {
            // 检查是否属于历史流程实例
            HistoricProcessInstance historicProcessInstance = this.getHistoricInstance(processInstanceId);
            if (null != historicProcessInstance) {
                return ProcessInstanceStatusEnum.END;
            }
            throw new AppException(MessageEnum.PARAM_ERROR.getCode(), "流程实例不存在");
        }
        if (processInstance.isEnded()) {
            return ProcessInstanceStatusEnum.END;
        }
        if (processInstance.isSuspended()) {
            return ProcessInstanceStatusEnum.SUSPENDED;
        }
        // 查询所有代办
        TaskQuery taskQuery = this.taskService.createTaskQuery();
        taskQuery.processInstanceId(processInstanceId);
        List<Task> taskList = taskQuery
                .taskVariableValueEquals(VariableEnum.TASK_STATUS.getValue(), ProcessInstanceStatusEnum.REJECT.getCode())
                .orderByTaskCreateTime().desc().list();
        if (CollUtil.isNotEmpty(taskList)) {
            return ProcessInstanceStatusEnum.REJECT;
        }
        return ProcessInstanceStatusEnum.ING;
    }

    /**
     * 查询活动节点
     *
     * @param processInstanceId 实例ID
     * @param finish            true=已完成的；false=未完成；null=全部
     * @return list
     */
    public List<HistoricActivityInstance> listActivityInstance(String processInstanceId, Boolean finish) {
        HistoricActivityInstanceQuery query = this.historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc();
        if (null == finish) {
            return query.list();
        }
        return Boolean.TRUE.equals(finish) ? query.finished().list() : query.unfinished().list();
    }

    /**
     * 获取节点间的分支线
     *
     * @param bpmnModelInstance    bpmn模型
     * @param activityInstanceList 节点列表
     * @return list
     */
    public List<SequenceFlow> listSequenceFlowByActivityInstance(BpmnModelInstance bpmnModelInstance, List<HistoricActivityInstance> activityInstanceList) {
        if (CollUtil.isEmpty(activityInstanceList)) {
            CollUtil.newArrayList();
        }
        ModelElementInstance domInstance;
        FlowNode flowNode;
        Set<String> activityIdSet = activityInstanceList.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toSet());
        List<SequenceFlow> result = CollUtil.newArrayList();
        for (HistoricActivityInstance activityInstance : activityInstanceList) {
            // 根据key获取bpmn元素
            domInstance = bpmnModelInstance.getModelElementById(activityInstance.getActivityId());
            // 转换成flowNode流程节点，获取输出线、输入线
            flowNode = (FlowNode) domInstance;
            Collection<SequenceFlow> outGoingList = flowNode.getOutgoing();
            outGoingList.stream().filter(f -> activityIdSet.contains(f.getTarget().getId())).forEach(result::add);
        }
        return result;
    }
}
